function mxConnectionHandler(graph, factoryMethod)
{
	if (graph != null)
	{
		this.graph = graph;
		this.factoryMethod = factoryMethod;
		this.init();

		if (document.body != null)
		{
			this.graph.addMouseListener(this);
		}
	}
};
mxConnectionHandler.prototype.graph = null;
mxConnectionHandler.prototype.factoryMethod = true;
mxConnectionHandler.prototype.connectImage = null;
mxConnectionHandler.prototype.targetConnectImage = false;
mxConnectionHandler.prototype.enabled = true;
mxConnectionHandler.prototype.select = true;
mxConnectionHandler.prototype.iconZIndex = 10005;
mxConnectionHandler.prototype.createTarget = false;
mxConnectionHandler.prototype.marker = null;
mxConnectionHandler.prototype.error = null;
mxConnectionHandler.prototype.ignoreMouseDown = false;
mxConnectionHandler.prototype.start = null;
mxConnectionHandler.prototype.edgeState = null;
mxConnectionHandler.prototype.isEnabled = function()
{
	return this.enabled;
};
mxConnectionHandler.prototype.setEnabled = function(enabled)
{
	this.enabled = enabled;
};
mxConnectionHandler.prototype.isCreateTarget = function()
{
	return this.createTarget;
};
mxConnectionHandler.prototype.setCreateTarget = function(value)
{
	this.createTarget = value;
};
mxConnectionHandler.prototype.init = function()
{
	if (this.graph.container != null)
	{
		this.marker = this.createMarker();
		this.shape = new mxPolyline([], mxConstants.INVALID_COLOR);
		this.shape.isDashed = true;
		this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG)
			? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
		this.shape.init(this.graph.getView().getOverlayPane());
		this.shape.node.style.display = 'none';

		if (this.graph.dialect != mxConstants.DIALECT_SVG)
		{
			mxEvent.redirectMouseEvents(this.shape.node, this.graph, null, null, true);
		}
		else
		{

			this.shape.pipe.setAttribute('style', 'pointer-events:none;');
			this.shape.innerNode.setAttribute('style', 'pointer-events:none;');
		}
		var self = this;
		var changeHandler = function(sender)
		{
			if (self.iconState != null)
			{
				self.iconState = self.graph.getView().getState(self.iconState.cell);
			}

			if (self.iconState != null)
			{
				self.redrawIcons(self.icons, self.iconState);
			}
			else
			{
				self.destroyIcons(self.icons);
				self.previous = null;
			}
		};
		this.graph.getModel().addListener(mxEvent.CHANGE, changeHandler);
		this.graph.getView().addListener(mxEvent.SCALE, changeHandler);
		this.graph.getView().addListener(mxEvent.TRANSLATE, changeHandler);
		this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, changeHandler);
		var drillHandler = function(sender)
		{
			self.destroyIcons(self.icons);
		};
		this.graph.addListener(mxEvent.START_EDITING, drillHandler);
		this.graph.getView().addListener(mxEvent.DOWN, drillHandler);
		this.graph.getView().addListener(mxEvent.UP, drillHandler);
	}
};
mxConnectionHandler.prototype.createMarker = function()
{
	var marker = new mxCellMarker(this.graph);
	marker.hotspotEnabled = true;
	var self = this;

	marker.getCell = function(evt, cell)
	{
		var cell = mxCellMarker.prototype.getCell.apply(this, arguments);
		self.error = null;

		if (cell != null)
		{
			if (self.isConnecting())
			{
				if (self.previous != null)
				{
					self.error = self.validateConnection(self.previous.cell, cell);

					if (self.error != null && self.error.length == 0)
					{
						cell = null;

						if (self.isCreateTarget())
						{
							self.error = null;
						}
					}
				}
			}
			else if (!self.isValidSource(cell))
			{
				cell = null;
			}
		}
		else if (self.isConnecting() && !self.isCreateTarget() && !self.graph.allowDanglingEdges)
		{
			self.error = '';
		}
		return cell;
	};
	marker.isValidState = function(state)
	{
		if (self.isConnecting())
		{
			return self.error == null;
		}
		else
		{
			return mxCellMarker.prototype.isValidState.apply(this, arguments);
		}
	};

	marker.getMarkerColor = function(evt, state, isValid)
	{
		return(self.connectImage == null || self.isConnecting())
			? mxCellMarker.prototype.getMarkerColor.apply(this, arguments) : null;
	};

	marker.intersects = function(state, evt)
	{
		if (self.connectImage != null || self.isConnecting())
		{
			return true;
		}
		return mxCellMarker.prototype.intersects.apply(this, arguments);
	};
	return marker;
};
mxConnectionHandler.prototype.isConnecting = function()
{
	return this.start != null && this.shape.node.style.display == 'inline';
};
mxConnectionHandler.prototype.isValidSource = function(cell)
{
	return this.graph.isValidSource(cell);
};
mxConnectionHandler.prototype.isValidTarget = function(cell)
{
	return true;
};
mxConnectionHandler.prototype.validateConnection = function(source, target)
{
	if (!this.isValidTarget(target))
	{
		return '';
	}
	return this.graph.getEdgeValidationError(null, source, target);
};
mxConnectionHandler.prototype.getConnectImage = function(state)
{
	return this.connectImage;
};
mxConnectionHandler.prototype.createIcons = function(state)
{
	var image = this.getConnectImage(state);

	if (image != null && state != null)
	{
		this.iconState = state;
		var icons = [];
		var bounds = new mxRectangle(0, 0, image.width, image.height);
		var icon = new mxImageShape(bounds, image.src);
		icon.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG)
			? mxConstants.DIALECT_STRICTHTML : mxConstants.DIALECT_VML;
		icon.init((this.graph.dialect == mxConstants.DIALECT_SVG)
			? this.graph.container : this.graph.getView().getOverlayPane());
		icon.node.style.cursor = (mxClient.IS_IE) ? 'all-scroll' : 'pointer';
		icon.node.style.zIndex = this.iconZIndex;
		var self = this;
		mxEvent.addListener(icon.node, 'dblclick', function(evt)
		{
			self.graph.dblClick(evt, state.cell);
			mxEvent.consume(evt);
		});
		mxEvent.addListener(icon.node, 'mousedown', function(evt)
		{
			self.icon = icon;
			self.graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
				new mxMouseEvent(evt, (self.currentState != null) ? self.currentState : state));
		});
		mxEvent.addListener(icon.node, 'mousemove', function(evt)
		{
			self.graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
				new mxMouseEvent(evt, (self.currentState != null) ? self.currentState : state));
		});
		mxEvent.addListener(icon.node, 'mouseup', function(evt)
		{
			self.graph.fireMouseEvent(mxEvent.MOUSE_UP,
				new mxMouseEvent(evt, (self.currentState != null) ? self.currentState : state));
		});
		icons.push(icon);
		this.redrawIcons(icons, this.iconState);
		return icons;
	}
	return null;
};
mxConnectionHandler.prototype.redrawIcons = function(icons, state)
{
	if (icons != null && icons[0] != null && state != null)
	{
		var pos = this.getIconPosition(icons[0], state);
		icons[0].bounds.x = pos.x;
		icons[0].bounds.y = pos.y;
		icons[0].redraw();
	}
};
mxConnectionHandler.prototype.getIconPosition = function(icon, state)
{
	var scale = this.graph.getView().scale;
	var cx = state.getCenterX();
	var cy = state.getCenterY();

	if (this.graph.isSwimlane(state.cell))
	{
		var size = this.graph.getStartSize(state.cell);
		cx = (size.width != 0) ? state.x + size.width * scale / 2 : cx;
		cy = (size.height != 0) ? state.y + size.height * scale / 2 : cy;
	}
	return new mxPoint(cx - icon.bounds.width / 2, cy - icon.bounds.height / 2);
};
mxConnectionHandler.prototype.destroyIcons = function(icons)
{
	if (icons != null)
	{
		this.iconState = null;

		for (var i = 0; i < icons.length; i++)
		{
			icons[i].destroy();
		}
	}
};
mxConnectionHandler.prototype.mouseDown = function(sender, me)
{
	if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled()
		&& !this.graph.isForceMarqueeEvent(me.getEvent()) && this.previous != null && this.error == null
			&& ((this.icons == null) || (this.icons != null && this.icon != null)) && me.getHandle() == null)
	{
		this.start = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());
		this.edgeState = this.createEdgeState(me);
		me.consume();
	}
	this.selectedIcon = this.icon;
	this.icon = null;
};
mxConnectionHandler.prototype.createEdgeState = function(me)
{
	return null;
};
mxConnectionHandler.prototype.mouseMove = function(sender, me)
{
	if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() && me.getHandle() == null
		&& (this.ignoreMouseDown || this.start != null || !this.graph.isMouseDown))
	{
		var state = this.marker.process(me);
		this.currentState = state;

		if (this.previous != null && this.start != null)
		{
			var view = this.graph.getView();
			var scale = view.scale;
			var pt = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());
			var point = new mxPoint(this.graph.snap(pt.x / scale) * scale, this.graph.snap(pt.y / scale) * scale);
			var current = point;

			if (state != null)
			{
				var targetPerimeter = view.getPerimeterFunction(state);

				if (targetPerimeter != null)
				{
					var next = new mxPoint(this.previous.getCenterX(), this.previous.getCenterY());
					var tmp =
						targetPerimeter(view.getPerimeterBounds(state, this.edgeState, false), this.edgeState, state,
							false, next);

					if (tmp != null)
					{
						current = tmp;
					}
				}
				else
				{
					current = new mxPoint(state.getCenterX(), state.getCenterY());
				}
			}

			if (this.selectedIcon != null)
			{
				var w = this.selectedIcon.bounds.width;
				var h = this.selectedIcon.bounds.height;

				if (state != null && this.targetConnectImage)
				{
					var pos = this.getIconPosition(this.selectedIcon, state);
					this.selectedIcon.bounds.x = pos.x;
					this.selectedIcon.bounds.y = pos.y;
				}
				else
				{
					var bounds = new mxRectangle(pt.x, pt.y + mxConstants.TOOLTIP_VERTICAL_OFFSET, w, h);
					this.selectedIcon.bounds = bounds;
				}
				this.selectedIcon.redraw();
			}
			var pt = this.start;
			var sourcePerimeter = view.getPerimeterFunction(this.previous);

			if (sourcePerimeter != null)
			{
				var tmp = sourcePerimeter(view.getPerimeterBounds(this.previous, this.edgeState, true), this.edgeState,
					this.previous, true, current);

				if (tmp != null)
				{
					pt = tmp;
				}
			}
			else
			{
				pt = new mxPoint(this.previous.getCenterX(), this.previous.getCenterY());
			}

			if (state == null)
			{

				var dx = current.x - pt.x;
				var dy = current.y - pt.y;
				var len = Math.sqrt(dx * dx + dy * dy);
				current.x -= dx * 4 / len;
				current.y -= dy * 4 / len;
			}
			this.shape.points =
			[
				pt,
				current
			];

			if (this.shape.node.style.display != 'inline')
			{
				var dx = Math.abs(point.x - this.start.x);
				var dy = Math.abs(point.y - this.start.y);

				if (dx > this.graph.tolerance || dy > this.graph.tolerance)
				{
					this.shape.node.style.display = 'inline';
				}
			}
			this.drawPreview();
			me.consume();
		}
		else if (this.previous != state)
		{
			if (this.previous != null && this.previous.shape != null)
			{
				this.previous.shape.node.style.cursor = this.previousCursor;
			}
			this.destroyIcons(this.icons);
			this.icons = null;

			if (state != null && this.error == null)
			{
				this.previousCursor = state.shape.node.style.cursor;
				state.shape.node.style.cursor = (mxClient.IS_IE) ? 'all-scroll' : 'default';
				this.icons = this.createIcons(state);
			}
			this.previous = state;
		}

		if (!this.graph.isMouseDown && state != null && this.icons != null)
		{
			var hitsIcon = false;
			var target = me.getTarget();

			for (var i = 0; i < this.icons.length && !hitsIcon; i++)
			{
				hitsIcon = target == this.icons[i].node || target.parentNode == this.icons[i].node;
			}

			if (!hitsIcon)
			{
				this.updateIcons(state, this.icons, me);
			}
		}
	}
};
mxConnectionHandler.prototype.updateIcons = function(state, icons, me)
{
};

mxConnectionHandler.prototype.mouseUp = function(sender, me)
{
	if (!me.isConsumed() && this.isConnecting())
	{
		if (this.error == null)
		{
			var source = this.previous.cell;
			var target = this.marker.hasValidState() ? this.marker.validState.cell : null;
			this.connect(source, target, me.getEvent(), me.getCell());
		}
		else
		{
			if (this.previous != null && this.marker.validState != null
				&& this.previous.cell == this.marker.validState.cell)
			{
				this.graph.selectCellForEvent(this.marker.source, evt);
			}

			if (this.error.length > 0)
			{
				this.graph.validationAlert(this.error);
			}
		}
		this.destroyIcons(this.icons);
		me.consume();
	}

	if (this.start != null)
	{
		this.reset();
	}
};
mxConnectionHandler.prototype.reset = function()
{
	this.shape.node.style.display = 'none';
	this.marker.reset();
	this.selectedIcon = null;
	this.edgeState = null;
	this.previous = null;
	this.error = null;
	this.start = null;
	this.icon = null;
};
mxConnectionHandler.prototype.drawPreview = function()
{
	var valid = this.error == null;
	var color = this.getEdgeColor(valid);

	if (this.shape.dialect == mxConstants.DIALECT_SVG)
	{
		this.shape.innerNode.setAttribute('stroke', color);
	}
	else
	{
		this.shape.node.setAttribute('strokecolor', color);
	}
	this.shape.strokewidth = this.getEdgeWidth(valid);
	this.shape.redraw();
	mxUtils.repaintGraph(this.graph, this.shape.points[1]);
};
mxConnectionHandler.prototype.getEdgeColor = function(valid)
{
	return(valid) ? mxConstants.VALID_COLOR : mxConstants.INVALID_COLOR;
};
mxConnectionHandler.prototype.getEdgeWidth = function(valid)
{
	return(valid) ? 3 : 1;
};
mxConnectionHandler.prototype.connect = function(source, target, evt, dropTarget)
{
	if (source != null && (target != null || this.isCreateTarget() || this.graph.allowDanglingEdges))
	{
		var model = this.graph.getModel();
		var edge = null;
		model.beginUpdate();

		try
		{
			if (target == null && this.isCreateTarget())
			{
				target = this.createTargetVertex(evt, source);

				if (target != null)
				{
					dropTarget = this.graph.getDropTarget([target], evt, dropTarget);

					if (dropTarget == null || !this.graph.getModel().isEdge(dropTarget))
					{
						var pstate = this.graph.getView().getState(dropTarget);

						if (pstate != null)
						{
							var tmp = model.getGeometry(target);
							tmp.x -= pstate.origin.x;
							tmp.y -= pstate.origin.y;
						}
					}
					else
					{
						dropTarget = this.graph.getDefaultParent();
					}
					this.graph.addCell(target, dropTarget);
				}
			}
			var parent = this.graph.getDefaultParent();

			if (model.getParent(source) == model.getParent(target))
			{
				parent = model.getParent(source);
			}

			var value = null;

			if (this.edgeState != null)
			{
				value = this.edgeState.cell.value;
			}
			edge = this.insertEdge(parent, null, value, source, target);

			if (edge != null)
			{
				var geo = model.getGeometry(edge);

				if (geo == null)
				{
					geo = new mxGeometry();
					geo.relative = true;
					model.setGeometry(edge, geo);
				}

				if (target == null)
				{
					var pt = this.graph.getPointForEvent(evt);
					geo.setTerminalPoint(pt, false);
				}
			}
		}
		finally
		{
			model.endUpdate();
		}

		if (this.select)
		{
			this.graph.setSelectionCell(edge);
		}
	}
};
mxConnectionHandler.prototype.insertEdge = function(parent, id, value, source, target)
{
	if (this.factoryMethod == null)
	{
		return this.graph.insertEdge(parent, id, value, source, target);
	}
	else
	{
		var edge = this.createEdge(value, source, target);
		edge = this.graph.addEdge(edge, parent, source, target);
		return edge;
	}
};
mxConnectionHandler.prototype.createTargetVertex = function(evt, source)
{
	var clone = this.graph.cloneCells([source])[0];
	var model = this.graph.getModel();
	var geo = model.getGeometry(clone);

	if (geo != null)
	{
		var point = this.graph.getPointForEvent(evt);
		geo.x = this.graph.snap(point.x - geo.width / 2);
		geo.y = this.graph.snap(point.y - geo.height / 2);
	}
	return clone;
};
mxConnectionHandler.prototype.createEdge = function(value, source, target)
{
	var edge = null;

	if (this.factoryMethod != null)
	{
		edge = this.factoryMethod(source, target);
	}

	if (edge == null)
	{
		edge = new mxCell(value || '');
		edge.setEdge(true);
		var geo = new mxGeometry();
		geo.relative = true;
		edge.setGeometry(geo);
	}
	return edge;
};
mxConnectionHandler.prototype.destroy = function()
{
	this.graph.removeMouseListener(this);

	if (this.shape != null)
	{
		this.shape.destroy();
		this.shape = null;
	}

	if (this.marker != null)
	{
		this.marker.destroy();
		this.marker = null;
	}
};